/*********************************************************************************
 *      Copyright:  (C) 2025 LingYun<iot25@lingyun>
 *                  All rights reserved.
 *
 *       Filename:  epoll.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(27/03/25)
 *         Author:  LingYun <iot25@lingyun>
 *      ChangeLog:  1, Release initial version on "27/03/25 12:55:05"
 *                 
 ********************************************************************************/
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<sys/socket.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>
#include<errno.h>
#include<time.h>
#include<getopt.h>
#include<libgen.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/epoll.h>
#include<sys/resource.h>
#include<ctype.h>

#define MAX_EVENTS      512
#define ARRAY_SIZE(x)   (sizeof(x)/sizeof(x[o]))   
int socket_server_init(char *listen_ip,int listen_port);
static inline void print_usage( char *progname);
void set_socket_rlimit(void);
int main(int argc ,char **argv)
{
	int 					listenfd , connfd;
	int 					serv_port = 0;
	int 					daemon_run = 0;
	char					*progname = NULL ;
	int						opt;
	int 					rv;
	int						i , j;
	char					buf[1024];

	int						epollfd;
	struct epoll_event		event;
	struct epoll_event		event_array[MAX_EVENTS];
	int 					events;

	struct option   long_options[]=
	{
		{ "daemon" , no_argument , NULL, 'b'},
		{ "port" , required_argument , NULL , 'p'},
		{ "help" , no_argument , NULL , 'h'},
		{  NULL , 0 , NULL , 0 }
	};

	progname=basename(argv[0]);
	
	while( (opt=getopt_long(argc, argv, "bp:h",long_options, NULL)) != -1)
	{
		switch(opt)
		{
			case 'b':
				daemon_run=1;
				break;
			
			case 'p':
				serv_port = atoi(optarg);
				break;

			case 'h':
				print_usage(progname);
				return EXIT_SUCCESS;

			default:
				break;
	    }
    }

	if( !serv_port )
	{
		print_usage(progname);
		return -1;
	}


	set_socket_rlimit();// 解除文件描述符数量限制

	if( (listenfd= socket_server_init(NULL, serv_port)) <0 )
    {
		printf(" ERROR: %s server listen on port %d failure\n", argv[0], serv_port);
		return -2;
    }
	printf(" %s server start to listen to on port %d\n",argv[0], serv_port);

	if( daemon_run )
	{
		daemon(0,0);
	}

	if( (epollfd=epoll_create(MAX_EVENTS)) < 0 )//epollfd:成功时返回的epoll文件描述符，用于后续操作
	{
		printf(" epoll_create() failure: %s\n", strerror(errno));
		return -3;
	}

	event.events = EPOLLIN;//EPOLLIN-可读事件
	event.data.fd = listenfd;//event.data.fd 关联的文件描述符（此处是监听套接字listenfd）

	if( epoll_ctl(epollfd , EPOLL_CTL_ADD,listenfd, &event) < 0 )//EPOLL_CTL_ADD	操作类型：新增监听,listenfd	要监听的套接字描述符 , &event	指向事件配置结构的指针
	{
		printf("epoll add listen socket  failure: %s\n", strerror(errno) );
		return -4;
	}

	for( ;  ; )
	{
		events = epoll_wait( epollfd, event_array, MAX_EVENTS, -1);//event_array:输出参数，存储就绪事件的数组,   MAX_EVENTS	:每次最多处理的事件数（防止数组溢出）   -1:	超时时间（毫秒），-1表示无限阻塞
		if(events < 0)
		{
			printf( "epoll failure: %s\n", strerror(errno));
			break;
		}
		else if( events == 0)
		{
			printf(" epoll get timeout\n");
			continue;
		}
/* 
 这段代码是 epoll事件循环的核心处理部分，主要完成：

遍历所有就绪事件（共events个）

检测错误事件（EPOLLERR或EPOLLHUP）

清理异常连接（移除epoll监听并关闭fd） 
									*/
		for(i=0; i<events ; i++)//events	epoll_wait返回的就绪事件数量
		{
			printf(" epoll_wait get error on fd[%d]: %s\n", event_array[i].data.fd, strerror(errno));//event_array	存储就绪事件的结构体数组，每个元素包含events和data.fd
			epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);//EPOLL_CTL_DEL		表示删除操作。 event_array[i].data.fd  要移除监听的目标文件描述符   NULL   删除操作时必须为NULL。
			close(event_array[i].data.fd);

		}
/*
 这段代码处理 监听套接字的新连接事件，核心逻辑：

检测到listenfd就绪时调用accept()接收新连接

将新连接的套接字（connfd）注册到epoll实例

错误处理：记录失败原因并继续事件循环*/

		if( event_array[i].data.fd == listenfd )//通过event_array[i].data.fd匹配预先注册的listenfd
		{
			if( ( connfd=accept(listenfd, (struct sockaddr *)NULL, NULL)) < 0 )//NULL, NULL        不获取客户端地址信息（生产环境应获取struct sockaddr_in用于日志/审计）
			{
				printf("accept new client failure: %s\n", strerror(errno));
				continue;
			}

			event.data.fd = connfd;
			event.events = EPOLLIN;// 默认水平触发模式，监听可读事件     // 后续调用 epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &event)
			
			if( epoll_ctl(epollfd, EPOLL_CTL_ADD,connfd, &event) < 0 )
			{
				printf( "epoll add client socket failure: %s\n", strerror(errno));
				close( event_array[i].data.fd);
				continue;
			}
			printf("epoll add new client socket[%d] ok\n", connfd);
		}
		else
		{
			if( (rv=read(event_array[i].data.fd , buf , sizeof(buf))) <= 0 )
			{
				printf("socket[%d] read failure or get diaconnect and will be remove. \n",event_array[i].data.fd);
				epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);//从epoll移除监听
				close(event_array[i].data.fd);
				continue;
			}
			else
			{
				printf(" socket[%d] read get %d bytes data\n", event_array[i].data.fd, rv);

				for(j=0; j<rv ;j++)
					buf[j]=toupper(buf[j]);//转大写

				if( write(event_array[i].data.fd, buf , rv) < 0 )//写入失败    rv:待发送数据长度（由前序read返回）    buf	待发送数据缓冲区（已转换为大写）
				{
					printf(" socket[%d] write failure: %s\n", event_array[i].data.fd, rv);
					epoll_ctl(epollfd,EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
					close(event_array[i].data.fd);
				}
			}
		}
	}
CleanUp:
	close(listenfd);
	return 0;
}

void set_socket_rlimit(void)
{
	struct rlimit limit = { 0 };

	getrlimit(RLIMIT_NOFILE, &limit );
	limit.rlim_cur = limit.rlim_max;
	setrlimit(RLIMIT_NOFILE,&limit );

	printf(" set socket open fd max count to %d\n",limit.rlim_max);
}

  //打印帮助信息
  static inline void print_usage( char *progname)
  {
      printf("Usage: %s [OPTION]...\n", progname);

      printf(" %s is a socket server program , which used to verify client and echo back               string         from it\n", progname);
      printf(" \n Mandatory arguments to long options are mandatory for short options too :\n");

      printf(" -b[daemon ] set program running on background\n");
      printf(" -p[port ] Socket server port address\n");
      printf(" -h[help ] Display this help information\n");

      printf(" \nExample: %s -b -p 8900\n", progname);
      return;
  }
 //初始化tcp服务器套接字
  int socket_server_init(char *listen_ip,int listen_port)
  {
      struct sockaddr_in          servaddr; //IPV4结构
      int                         rv = 0;
      int                         on = 1;//套接字选项开关
      int                         listenfd;//监听套接字描述符

      if( (listenfd = socket(AF_INET, SOCK_STREAM , 0 )) < 0 )//创建套接字
      {         printf(" use socket() to create a TCP socket failure: %s\n", strerror(errno));
          return -1;
      }

      setsockopt( listenfd, SOL_SOCKET, SO_REUSEADDR, &on ,                                     sizeof(on));//                              设置端口复用（防止TIME_WAIT状态导致连接失败）

      memset(&servaddr, 0 , sizeof(servaddr) );
      servaddr.sin_family = AF_INET;// IP v4协议
      servaddr.sin_port = htons(listen_port);//设置端口（主机字节序转成网络字节序）

      if( !listen_ip )//监听所有IP或指定IP
      {
          servaddr.sin_addr.s_addr = htonl(INADDR_ANY );
      }
      else//将字符串转换为二进制格式
      {
          if(inet_pton(AF_INET,listen_ip,&servaddr.sin_addr) <= 0)
          {
              printf("inet_pton() set listen IP address failure\n");
              rv = -2;
              goto Cleanup;
          }
      }
  //绑定套接字到IP端口
      if( bind( listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr) )<0 )
      {
          printf(" use bind() to bind the TCP socket failure: %s\n",strerror(errno) );
          rv = -3;
          goto Cleanup;
      }

      //开始监听，最大排队数位64
      if( listen(listenfd, 64)<0 )
      {
          printf(" use bind() to bind the TCP socket failure: %s\n",strerror(errno) );

          rv = -4;
          goto Cleanup;
      }
  Cleanup:
      if( rv<0 )
          close(listenfd);//失败时关闭套接字
      else
          rv = listenfd;//成功返回监听套接字

      return rv;

}

